package amap

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"gitee.com/xslasd/go-map/ls"
)

const (
	// 地理编码服务
	GeocoderUrl = "https://restapi.amap.com/v3/geocode/geo?parameters"
	// 全球逆地理编码服务Url
	ReverseGeocodingUrl = "https://restapi.amap.com/v3/geocode/regeo?parameters"
	// 普通IP定位
	IPLocationUrl = "https://restapi.amap.com/v3/ip?parameters"
	// 智能硬件定位
	LBSLocationUrl = "http://apilocate.amap.com/position"
	// 天气
	WeatherUrl = "https://restapi.amap.com/v3/weather/weatherInfo?parameters"
)

type AMapSDK struct {
	key    string
	output string
}

func NewAMapSDK(key, output string) *AMapSDK {
	return &AMapSDK{key: key, output: output}
}
func DefaultAMapSDK(key string) *AMapSDK {
	return &AMapSDK{key: key, output: "json"}
}

func (s AMapSDK) Geocoder(address string, city string) (*ls.Location, error) {
	params := url.Values{}
	params.Set("address", address)
	params.Set("city", city)
	return s.GeocoderParams(params)
}

func (s AMapSDK) GeocoderParams(params url.Values) (*ls.Location, error) {
	Url, _ := url.Parse(GeocoderUrl)
	params.Set("key", s.key)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	log.Printf("%s\n", body)
	out := new(GeocoderRes)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != "1" {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	if len(out.Geocodes) == 0 {
		return nil, fmt.Errorf("Geocode no result")
	}
	l := strings.Split(out.Geocodes[0].Location, ",")
	lng, err := strconv.ParseFloat(l[0], 64)
	if err != nil {
		return nil, err
	}
	lat, err := strconv.ParseFloat(l[1], 64)
	if err != nil {
		return nil, err
	}
	res := &ls.Location{
		Lng: lng,
		Lat: lat,
	}
	return res, nil
}

func (s AMapSDK) ReverseGeocoding(lng, lat float64) (*ls.Address, error) {
	params := url.Values{}
	params.Set("location", fmt.Sprintf("%v,%v", lng, lat))
	return s.ReverseGeocodingParams(params)
}
func (s AMapSDK) ReverseGeocodingParams(params url.Values) (*ls.Address, error) {
	Url, _ := url.Parse(ReverseGeocodingUrl)
	params.Set("key", s.key)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	log.Printf("%s\n", body)
	out := new(ReverseGeocodingRes)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != "1" {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	city, _ := out.Regeocodes.AddressComponent.City.(string)
	street, _ := out.Regeocodes.AddressComponent.StreetInfo.Street.(string)
	streetNumber, _ := out.Regeocodes.AddressComponent.StreetInfo.StreetNumber.(string)
	district, _ := out.Regeocodes.AddressComponent.District.(string)
	town, _ := out.Regeocodes.AddressComponent.Town.(string)
	tc, _ := out.Regeocodes.AddressComponent.TownCode.(string)
	res := &ls.Address{
		Province:     out.Regeocodes.AddressComponent.Province,
		City:         city,
		District:     district,
		Town:         town,
		TownCode:     tc,
		Street:       street,
		StreetNumber: streetNumber,
	}
	res.FormattedAddress = fmt.Sprintf("%s%s%s%s%s%s", res.Province, res.City, res.District, res.Town, res.Street, res.StreetNumber)
	adcode, err := strconv.ParseInt(out.Regeocodes.AddressComponent.AdCode, 10, 64)
	if err != nil {
		return nil, err
	}
	res.AdCode = adcode
	pois := make([]*ls.PoiInfo, 0)
	for _, item := range out.Regeocodes.Pois {
		obj := &ls.PoiInfo{
			UID:       item.UID,
			Addr:      item.Addr,
			Direction: item.Direction,
			Distance:  item.Distance,
			Name:      item.Name,
			Tag:       item.Tag,
			Tel:       item.Tel,
		}
		l := strings.Split(item.Location, ",")
		lng, err := strconv.ParseFloat(l[0], 64)
		if err != nil {
			return nil, err
		}
		lat, err := strconv.ParseFloat(l[1], 64)
		if err != nil {
			return nil, err
		}
		obj.Location.Lng = lng
		obj.Location.Lat = lat
		pois = append(pois, obj)
	}
	res.Pois = pois
	return res, nil
}

func (s AMapSDK) IPLocation(ip string) (*ls.IPPosition, error) {
	params := url.Values{}
	params.Set("ip", ip)
	return s.IPLocationParams(params)
}
func (s AMapSDK) IPLocationParams(params url.Values) (*ls.IPPosition, error) {
	Url, _ := url.Parse(IPLocationUrl)
	params.Set("key", s.key)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	out := new(IPLocationRes)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != "1" {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	res := &ls.IPPosition{
		Province: out.Province,
		City:     out.City,
	}
	return res, nil
}

func (s AMapSDK) LBSLocation(req *ls.LBSInfo) (*ls.LBSPosition, error) {
	params := url.Values{}
	params.Set("accesstype", strconv.Itoa(req.AccessType))
	params.Set("imei", req.IMEI)
	params.Set("cdma", strconv.Itoa(req.CDMA))
	params.Set("network", req.Network)
	params.Set("bts", req.BTS)
	params.Set("imsi", req.IMSI)
	params.Set("smac", req.SMAC)
	params.Set("serverip", req.ClientIP)
	params.Set("tel", req.TEL)
	params.Set("mmac", req.MMAC)

	return s.LBSLocationParams(params)
}
func (s AMapSDK) LBSLocationParams(params url.Values) (*ls.LBSPosition, error) {
	Url, _ := url.Parse(LBSLocationUrl)

	params.Set("key", s.key)
	params.Set("output", s.output)
	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	log.Printf("url: %s\n", Url.String())
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	out := new(LBSLocationRes)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}
	if out.Status != "1" {
		return nil, fmt.Errorf("%v:%s", out.Status, out.Msg)
	}
	if out.Result.Type == "0" {
		return &ls.LBSPosition{}, nil
	}

	r, err := strconv.Atoi(out.Result.Radius)
	if err != nil {
		return nil, err
	}

	ac, err := strconv.ParseInt(out.Result.AdCode, 10, 64)
	if err != nil {
		return nil, err
	}

	res := &ls.LBSPosition{
		Radius:           r,
		FormattedAddress: out.Result.FormattedAddress,
		AdCode:           ac,
	}
	l := strings.Split(out.Result.Location, ",")
	lng, err := strconv.ParseFloat(l[0], 64)
	if err != nil {
		return nil, err
	}
	lat, err := strconv.ParseFloat(l[1], 64)
	if err != nil {
		return nil, err
	}
	res.Location.Lng = lng
	res.Location.Lat = lat

	a, err := s.ReverseGeocoding(lng, lat)
	if err != nil {
		return nil, err
	}
	res.Province = a.Province
	res.City = a.City
	res.District = a.District

	return res, nil
}

func (s AMapSDK) GetWeatherNow(adcode int64) (*ls.WeatherInfo, error) {
	Url, _ := url.Parse(WeatherUrl)
	params := url.Values{}
	params.Set("key", s.key)
	params.Set("city", strconv.FormatInt(adcode, 10))
	params.Set("extensions", "base")
	params.Set("output", s.output)

	Url.RawQuery = params.Encode()
	resp, err := http.Get(Url.String())
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	log.Printf("url: %s\n", Url.String())
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	out := new(WeatherResponse)
	err = json.Unmarshal(body, &out)
	if err != nil {
		return nil, err
	}

	if out.Status != "1" {
		return nil, fmt.Errorf("error response, code: %s, info: %s", out.InfoCode, out.Info)
	}

	if len(out.Lives) == 0 {
		return nil, fmt.Errorf("lives is empty")
	}

	return &ls.WeatherInfo{
		Province:      out.Lives[0].Province,
		City:          out.Lives[0].City,
		Adcode:        out.Lives[0].Adcode,
		Weather:       out.Lives[0].Weather,
		Temperature:   out.Lives[0].Temperature,
		WindDirection: out.Lives[0].WindDirection,
		WindPower:     out.Lives[0].WindPower,
		Humidity:      out.Lives[0].Humidity,
		ReportTime:    out.Lives[0].ReportTime,
	}, nil
}
